﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Ndo.Maths
{
    public interface IStatistic<TReal>
        where TReal: Real<TReal>, new()
    {
        TReal Max { get; }
        TReal Min { get; }
        TReal Mean { get; }
        TReal Count { get; }
        TReal Sum { get; }
        TReal S2 { get; }
        void Inform(TReal value);
    }

    public class Statistic<TReal> : IStatistic<TReal>
        where TReal : Real<TReal>, new()
    {
        public Statistic()
        {}
        public Statistic(IEnumerable<TReal> data)
        {
            foreach (TReal number in data)
            {
                Inform(number);
            }
        }

        private readonly List<TReal> data = new List<TReal>();
        private TReal count = Number<TReal>.Zero;
        private TReal sum = Number<TReal>.Zero;
        private TReal max = Number<TReal>.Zero;
        private TReal min = Number<TReal>.Zero;
        private bool meandirty = false;
        private TReal mean;
        private bool s2dirty = false;
        private TReal s2;


        public IList<TReal> Data
        {
            get
            {
                return data.ToArray();
            }
        }

        public TReal Count
        {
            get { return count; }
        }

        public TReal Sum
        {
            get { return sum; }
        }

        public TReal Max
        {
            get
            {
                if (count == Number<TReal>.Zero)
                    throw new InvalidOperationException();
                return max;
            }
        }
        public TReal Min
        {
            get
            {
                if (count == Number<TReal>.Zero)
                    throw new InvalidOperationException();
                return min;
            }
        }

        public TReal Mean
        {
            get
            {
                if(count == Number<TReal>.Zero)
                    throw new InvalidOperationException();
                if(meandirty)
                {
                    mean = sum.DividedBy(count);
                    meandirty = false;
                }
                return mean;
            }
        }

        public TReal S2
        {
            get
            {
                if (count == Number<TReal>.Zero)
                    throw new InvalidOperationException();
                if (s2dirty)
                {
                    s2 = Number<TReal>.Zero;
                    foreach (TReal number in data)
                    {
                        s2 = s2.Add(number.SubtractedFrom(mean).GetSqr());
                    }
                    s2 = s2.DividedBy(count);
                }
                return s2;
            }
        }

        public void Inform(TReal value)
        {
            data.Add(value);
            if(count == Number<TReal>.Zero)
            {
                min = value;
                max = value;
            }
            else
            {
                if(value.GreaterThan(max))
                {
                    max = value;
                }
                else if(value.LessThan(min))
                {
                    min = value;
                }
            }
            count = count.GetIncrement();
            sum = sum.Add(value);
            meandirty = true;
            s2dirty = true;
        }
    }
}
